<template>
	<view
		class="xt-form-main"
		:class="{
			'xt-form-disabled': disabled
		}"
	>
		<slot></slot>
	</view>
</template>
<script>
import props from './props.js';
import Schema from './async-validator.js';
// 去除警告信息
Schema.warning = function () {};
/**
 * Form 表单
 * @description 本组件提供一个表单的功能，让开发者开发起来更加得心应手。减少重复的模板代码
 * @tutorial https://ext.dcloud.net.cn/plugin?name=xt-form
 *
 * @property {Object}          		model             		需要验证字段的集合
 * @property {String | Number}      labelWidth             	提示文字的宽度(默认 65rpx)
 * @property {String}          		labelAlign             	lable字体的位置(默认 left)
 * @property {Object}          		labelStyle             	lable的样式
 * @property {Boolean}          	borderBottom            是否显示表单项的下划线边框(默认 false)
 * @property {String}          		gap             		表单项之间的间距(默认 20rpx)
 * @property {Object}          		rules             		校验规则
 * @property {Boolean}          	disabled             	是否禁用整个表单(默认 false)
 * @example <xt-form></xt-form>
 */
export default {
	name: 'xt-form',
	mixins: [props],
	data() {
		return {
			// 原始数据
			oldData: null,
			// 校验规则
			formRules: {},
			// 校验器
			validator: {}
		};
	},
	watch: {
		model: {
			immediate: true,
			handler(newVal) {
				if (!this.oldData) {
					this.oldData = this.deepClone(newVal);
				}
			}
		},
		// 监听规则的变化
		rules: {
			immediate: true,
			handler(newVal) {
				if (!newVal) return;
				this.setRules(newVal);
			}
		}
	},
	created() {
		this.children = [];
	},
	beforeDestroy() {
		if (this.parent && this.array(this.parent.children)) {
			const childrenList = this.parent.children;
			childrenList.map((child, index) => {
				if (child === this) {
					childrenList.splice(index, 1);
				}
			});
		}
	},
	methods: {
		/**
		 * @description 深度克隆
		 * @param {object} obj 需要深度克隆的对象
		 * @param cache 缓存
		 * @returns {*} 克隆后的对象或者原值（不是对象）
		 */
		deepClone(obj, cache = new WeakMap()) {
			if (obj === null || typeof obj !== 'object') return obj;
			if (cache.has(obj)) return cache.get(obj);
			let clone;
			if (obj instanceof Date) {
				clone = new Date(obj.getTime());
			} else if (obj instanceof RegExp) {
				clone = new RegExp(obj);
			} else if (obj instanceof Map) {
				clone = new Map(Array.from(obj, ([key, value]) => [key, this.deepClone(value, cache)]));
			} else if (obj instanceof Set) {
				clone = new Set(Array.from(obj, (value) => this.deepClone(value, cache)));
			} else if (Array.isArray(obj)) {
				clone = obj.map((value) => this.deepClone(value, cache));
			} else if (Object.prototype.toString.call(obj) === '[object Object]') {
				clone = Object.create(Object.getPrototypeOf(obj));
				cache.set(obj, clone);
				for (const [key, value] of Object.entries(obj)) {
					clone[key] = this.deepClone(value, cache);
				}
			} else {
				clone = Object.assign({}, obj);
			}
			cache.set(obj, clone);
			return clone;
		},
		/**
		 * @description 设置校验规则
		 */
		setRules(rules) {
			if (Object.keys(rules).length === 0) return;
			this.formRules = rules;
			this.validator = new Schema(rules);
		},
		/**
		 * @description 获取某个对象下的属性
		 * @param {object} obj 对象
		 * @param {string} key 需要获取的属性字段
		 * @returns {*}
		 */
		getProperty(obj, key) {
			if (!obj) {
				return;
			}
			if (typeof key !== 'string' || key === '') {
				return '';
			}
			if (key.indexOf('.') !== -1) {
				const keys = key.split('.');
				let firstObj = obj[keys[0]] || {};

				for (let i = 1; i < keys.length; i++) {
					if (firstObj) {
						firstObj = firstObj[keys[i]];
					}
				}
				return firstObj;
			}
			return obj[key];
		},
		/**
		 * @description 设置对象的属性值，如果'a.b.c'的形式进行设置
		 * @param {object} obj 对象
		 * @param {string} key 需要设置的属性
		 * @param {string} value 设置的值
		 */
		setProperty(obj, key, value) {
			if (!obj) {
				return;
			}
			// 递归赋值
			const inFn = function (_obj, keys, v) {
				// 最后一个属性key
				if (keys.length === 1) {
					_obj[keys[0]] = v;
					return;
				}
				// 0~length-1个key
				while (keys.length > 1) {
					const k = keys[0];
					if (!_obj[k] || typeof _obj[k] !== 'object') {
						_obj[k] = {};
					}
					const key = keys.shift();
					// 自调用判断是否存在属性，不存在则自动创建对象
					inFn(_obj[k], keys, v);
				}
			};

			if (typeof key !== 'string' || key === '') {
			} else if (key.indexOf('.') !== -1) {
				// 支持多层级赋值操作
				const keys = key.split('.');
				inFn(obj, keys, value);
			} else {
				obj[key] = value;
			}
		},
		array(value) {
			if (typeof Array.isArray === 'function') {
				return Array.isArray(value);
			}
			return Object.prototype.toString.call(value) === '[object Array]';
		},
		resetFields() {
			const that = this;
			this.children.map((child) => {
				const prop = child?.prop;
				const value = this.getProperty(that.oldData, prop);
				this.setProperty(this.model, prop, value);
			});
		},
		// 清空校验结果
		clearValidate(props) {
			props = [].concat(props);
			this.children.map((child) => {
				if (props[0] === undefined || props.includes(child.prop)) {
					child.message = null;
				}
			});
		},
		/**
		 * @description 校验单个表单
		 */
		async validateField(value, callback, event = null) {
			const that = this;
			// $nextTick是必须的，否则model的变更，可能会延后于此方法的执行
			this.$nextTick(() => {
				// 校验错误信息，返回给回调方法，用于存放所有form-item的错误信息
				const errorsRes = [];
				// 如果为字符串，转为数组
				value = [].concat(value);
				// 历遍children所有子form-item
				let promises = this.children.map((child) => {
					return new Promise((resolve, reject) => {
						// 用于存放错误信息
						const childErrors = [];
						if (value.includes(child.prop)) {
							const propertyVal = this.getProperty(this.model, child.prop);
							// 属性链数组
							const propertyChain = child.prop.split('.');
							const propertyName = propertyChain[propertyChain.length - 1];

							let rule = this.formRules[child.prop];
							// 如果不存在对应的规则，直接返回，否则校验器会报错
							if (!rule) {
								resolve();
								return;
							}
							// rule规则可为数组形式，也可为对象形式，此处拼接成为数组
							const rules = [].concat(rule);

							// 对rules数组进行校验
							if (!rules.length) {
								resolve();
							}
							for (let i = 0; i < rules.length; i++) {
								const ruleItem = rules[i];
								const trigger = [].concat(ruleItem?.trigger);

								if (event && !trigger.includes(event)) {
									resolve();
									continue;
								}
								// 实例化校验对象，传入构造规则
								const validator = new Schema({
									[propertyName]: ruleItem
								});
								validator.validate(
									{
										[propertyName]: propertyVal
									},
									(errors, fields) => {
										if (that.array(errors)) {
											errors.forEach((element) => {
												element.prop = child.prop;
											});
											errorsRes.push(...errors);
											childErrors.push(...errors);
										}
										child.message = childErrors[0]?.message ? childErrors[0].message : null;
										if (i == rules.length - 1) {
											resolve(errorsRes);
										}
									}
								);
							}
						} else {
							resolve({});
						}
					});
				});

				// 使用Promise.all来等待所有Promise完成
				Promise.all(promises)
					.then((results) => {
						// 执行回调函数
						typeof callback === 'function' && callback(errorsRes);
					})
					.catch((error) => {
						console.error('错误:', error);
					});
			});
		},
		/**
		 * @description 校验全部必填项
		 */
		validate() {
			return new Promise((resolve, reject) => {
				this.$nextTick(() => {
					if (Object.keys(this.formRules).length === 0) return resolve(true);
					const formItemProps = this.children.map((item) => item.prop);
					this.validateField(formItemProps, (errors) => {
						if (errors.length) {
							reject(errors);
						} else {
							resolve(true);
						}
					});
				});
			});
		}
	}
};
</script>
<style scoped lang="scss">
@import './style.scss';
</style>
